package com.autonavi.collapse.task.context.utils;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.net.URL;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

public class ResolverUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(ResolverUtil.class);

    private static final byte[] JAR_MAGIC = {'P', 'K',3 , 4};

    public static List<Class<?>> loadClasses(String... packageNames) throws Exception{
        List<Class<?>> classes = new ArrayList<>();
        for (String packageName : packageNames){
            String path = getPackagePath(packageName);
            try{
                List<String> children = ResolverUtil.list(path);
                for (String child : children) {
                    if (child.endsWith(".class")) {
                        String externalName = child.substring(0, child.indexOf(".")).replace("/",".");
                        ClassLoader loader = Thread.currentThread().getContextClassLoader();
                        try {
                            Class<?> type = loader.loadClass(externalName);
                            classes.add(type);
                        } catch (ClassNotFoundException e) {
                            LOGGER.error(String.format("%s can not found", e.getMessage()));
                        }
                    }
                }
            }catch (Exception e){
                LOGGER.error("Could not read package: " + packageName, e);
            }
        }
        return classes;
    }

    public static List<String> list(String path) throws IOException {
        List<String> names = new ArrayList<>();
        for (URL url : getResources(path)){
            names.addAll(list(url,path));
        }
        return names;
    }

    private static List<String> list(URL url, String path) throws IOException{
        InputStream is = null;
        try{
            List<String> resources = new ArrayList<>();
            URL jarUrl = findJarForResource(url);
            if (jarUrl != null){
                is = jarUrl.openStream();
                LOGGER.debug("Listing " + url);
                resources = listResources(new JarInputStream(is), path);
            }else {
                List<String> children = new ArrayList<>();
                try {
                    if(isJar(url)){
                        is = url.openStream();
                        JarInputStream jarInput = new JarInputStream(is);
                        LOGGER.debug("Listing " + url);
                        for (JarEntry entry; (entry = jarInput.getNextJarEntry()) != null;){
                            LOGGER.debug("Jar entry: " + entry.getName());
                            children.add(entry.getName());
                        }
                    }else{
                        is = url.openStream();
                        BufferedReader reader = new BufferedReader(new InputStreamReader(is, StandardCharsets.UTF_8));
                        List<String> lines = new ArrayList<>();
                        for (String line; (line = reader.readLine()) != null;) {
                            LOGGER.debug("Reader entry: " + line);
                            lines.add(line);
                            if (getResources(path + "/" + line).isEmpty()) {
                                lines.clear();
                                break;
                            }
                        }
                        if (!lines.isEmpty()) {
                            LOGGER.debug("Listing " + url);
                            children.addAll(lines);
                        }
                    }
                }catch (FileNotFoundException e) {
                    if ("file".equals(url.getProtocol())) {
                        File file = new File(url.getFile());
                        LOGGER.debug("Listing directory " + file.getAbsolutePath());
                        if (file.isDirectory()) {
                            LOGGER.debug("Listing " + url);
                            children = Arrays.asList(file.list());
                        }
                     }else {
                        throw e;
                    }
                }
                String prefix = url.toExternalForm();
                if (!prefix.endsWith("/")) {
                    prefix = prefix + "/";
                }
                for (String child : children) {
                    String resourcePath = path + "/" + child;
                    resources.add(resourcePath);
                    URL childUrl = new URL(prefix + child);
                    resources.addAll(list(childUrl, resourcePath));
                }
            }
            return resources;
        }finally {
            try {
                if (is != null) {
                    is.close();
                }
            }catch (Exception e) {

            }
        }
    }

    private static List<String> listResources(JarInputStream jar, String path) throws IOException {
        if (!path.startsWith("/")){
            path = "/" + path;
        }
        if (!path.endsWith("/")){
            path = path + "/";
        }
        List<String> resources = new ArrayList<>();
        for (JarEntry entry; (entry = jar.getNextJarEntry()) != null;){
            if (!entry.isDirectory()) {
                String name = entry.getName();
                if (!name.startsWith("/")) {
                    name = "/" + name;
                }
                if (name.startsWith(path)) {
                    LOGGER.debug("Found resource: " + name);
                    resources.add(name.substring(1));
                }
            }
        }
        return resources;
    }

    private static URL findJarForResource(URL url) {
        LOGGER.debug("Find JAR URL : " + url);
        try{
            for (; ; ){
                url = new URL(url.getFile());
                LOGGER.debug("Inner URL:" + url);
            }
        }catch (Exception e){
            //e.printStackTrace();
        }
        StringBuilder jarUrl = new StringBuilder(url.toExternalForm());
        int index = jarUrl.lastIndexOf(".jar");
        if (index>=0){
            jarUrl.setLength(index + 4);
            LOGGER.debug("Extracted JAR URL: " + jarUrl);
        }else {
            LOGGER.debug("Not a JAR: " + jarUrl);
            return null;
        }
        try{
            URL testUrl = new URL(jarUrl.toString());
            if(isJar(testUrl)){
                return testUrl;
            }else {
                LOGGER.debug("Not a JAR: " + jarUrl);
                jarUrl.replace(0, jarUrl.length(), testUrl.getFile());
                File file = new File(jarUrl.toString());
                if(!file.exists()){
                    try {
                        file = new File(URLEncoder.encode(jarUrl.toString()),"UTF-8");
                    }catch (Exception e){
                        throw new RuntimeException("Unsupported excording? UTF-8? That's unpossinle.");
                    }
                }
                if (file.exists()){
                    LOGGER.debug("Trying real file: " + file.getAbsolutePath());
                    testUrl = file.toURI().toURL();
                    if(isJar(testUrl)){
                        return testUrl;
                    }
                }
            }
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    private static boolean isJar(URL url) {
        return isJar(url, new byte[JAR_MAGIC.length]);
    }

    private static boolean isJar(URL url, byte[] buffer) {
        InputStream is = null;
        try {
            is = url.openStream();
            is.read(buffer, 0, JAR_MAGIC.length);
            if(Arrays.equals(buffer, JAR_MAGIC)){
                LOGGER.debug("Found JAR: " + url);
                return true;
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            try{
                is.close();
            }catch (Exception e){

            }
        }
        return false;
    }

    private static List<URL> getResources(String path) throws IOException {
        return Collections.list(Thread.currentThread().getContextClassLoader().getResources(path));
    }

    protected static String getPackagePath(String packageName){
        return packageName == null ? null : packageName.replace(".","/");
    }

}
